﻿using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Pyramid.Hardware;
using Pyramid.Hardware.Interop;
using Pyramid.Hardware.Operations;

namespace Pyramid.Tests
{
    [TestClass]
    public class AddOperationFixture
    {
        [TestMethod]
        public void ShouldPerformAddOperation()
        {
            for (int left = -10; left < 10; left++)
            {
                for (int right = -10; right < 10; right++)
                {
                    TestAddition(left, right);
                }
            }
        }

        [TestMethod]
        public void ShouldSetSignFlagOnPositiveResult()
        {
            Tryte inputLeft = 0;
            Tryte inputRight = 1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(1, (int)testOperation.SignFlag.Signal);
        }

        [TestMethod]
        public void ShouldSetSignFlagOnNegativeResult()
        {
            Tryte inputLeft = 0;
            Tryte inputRight = -1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(-1, (int)testOperation.SignFlag.Signal);
        }

        [TestMethod]
        public void ShouldSetSignFlagOnZero()
        {
            Tryte inputLeft = 1;
            Tryte inputRight = -1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(0, (int)testOperation.SignFlag.Signal);
        }

        [TestMethod]
        public void ShouldSetOverflowFlagOnPositiveOverflow()
        {
            Tryte inputLeft = Tryte.MaxValue;
            Tryte inputRight = 1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(1, (int)testOperation.OverflowFlag.Signal);
        }

        [TestMethod]
        public void ShouldSetOverflowFlagOnNegativeOverflow()
        {
            Tryte inputLeft = Tryte.MinValue;
            Tryte inputRight = -1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(-1, (int)testOperation.OverflowFlag.Signal);
        }

        [TestMethod]
        public void ShouldNotSetOverflowFlagOnNormalResult()
        {
            Tryte inputLeft = 0;
            Tryte inputRight = 1;

            var testOperation = CreateAddOperation(inputLeft, inputRight);

            Assert.AreEqual(0, (int)testOperation.OverflowFlag.Signal);
        }

        [TestMethod]
        public void ShouldPerformAddWithVaryingInput()
        {
            var inputA = new VariableInputBus(Tryte.TritsPerTryte);
            var inputB = new VariableInputBus(Tryte.TritsPerTryte);

            inputA.SetValue((Tryte)4);
            inputB.SetValue((Tryte)6);

            int expectedResult = 10;

            var clock = new ManualClock();
            var testOperation = new AddOperation(inputA.OutputBus, inputB.OutputBus);
            var outputBus = new OutputBus(testOperation.OutputBus);

            Assert.AreEqual(expectedResult, (int)outputBus.Value.ToTryte());

            inputA.SetValue((Tryte)100);
            inputB.SetValue((Tryte)40);

            expectedResult = 140;

            Assert.AreEqual(expectedResult, (int)outputBus.Value.ToTryte());
        }

        private static void TestAddition(int left, int right)
        {
            try
            {
                var inputLeft = (Tryte)left;
                var inputRight = (Tryte)right;
                var expectedResult = (Tryte)(left + right);

                var testOperation = CreateAddOperation(inputLeft, inputRight);
                var outputBus = new OutputBus(testOperation.OutputBus);

                Assert.AreEqual(expectedResult, outputBus.Value.ToTryte());

                Assert.AreEqual(Math.Sign(expectedResult), (int)testOperation.SignFlag.Signal);

                Assert.AreEqual(0, (int)testOperation.OverflowFlag.Signal);
            }
            catch (AssertFailedException ex)
            {
                throw new AssertFailedException(string.Format("Failure whilst adding {0} & {1}: " + ex.Message, left, right), ex);
            }
        }

        private static AddOperation CreateAddOperation(Tryte inputLeft, Tryte inputRight)
        {
            return new AddOperation(FixedInputLine.CreateBus(inputLeft).Cast<ILine>(), FixedInputLine.CreateBus(inputRight).Cast<ILine>());
        }
    }
}
